<?php

namespace LowCode\Core;

use BadMethodCallException;
use DI\Bridge\Slim\Bridge;
use DI\Container;
use Dotenv\Dotenv;
use LowCode\Asset;
use LowCode\Settings;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Smarty\Exception;


/**
 *
 */
class Admin
{
    private static Admin $instance;
    private ?App $app = null;
    private Manifest $manifest;
    private View $view;
    private array $systemUser = [];
    private static array $config = [];

    public function __construct()
    {
        $this->register();
    }

    public static function factory(): Admin
    {
        if (!isset(self::$instance) || !(self::$instance instanceof self)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function register(): void
    {
        $this->registerEnv();
        $this->registerConfig();
        $this->manifest = Manifest::factory();
        $this->view = View::factory();
        if (!isRunInLaravel()){
            $this->registerApp();
            $this->registerRouter();
        }
    }

    private function registerApp(): void
    {
        $container = new Container(self::$config);
        $container->set(View::class, function (ContainerInterface $container) {
            return $this->view;
        });
        $this->app = Bridge::create($container);
    }

    private function registerEnv(): void
    {

        $envFile1 = getLowCodePath("/.env");
        $env1 = file_exists($envFile1) && is_file($envFile1);
        $envFile2 = self::publicPath("/../.env");
        $env2 = file_exists($envFile2) && is_file($envFile2);
        $paths = [];
        if ($env1){
            $paths[] = getLowCodePath("/");
        }
        if ($env2){
            $paths[] = self::publicPath("/../");
        }
        if ($paths){
            $dotenv = Dotenv::createImmutable($paths);
            $dotenv->load();
        }
    }

    private function registerConfig(): void
    {
        $config = require(self::getConfigPath("lowcode.php"));
        $config = array_merge($config, self::$config);
        if (isRunInLaravel()){
            self::$config = array_merge($config, config('lowcode'));
        }else{
            self::$config = $config;
        }
    }

    private function registerRouter(): void
    {
        $publicName = self::getConfig('public');
        $patternName = "/".trim($publicName,"/").".php";
        $this->app->get($patternName,function (Request $request, Response $response) {
            $response->getBody()->write($this->getWeb());
            return $response;
        });
    }

    /**
     * @return Settings
     */
    public static function theme(): Settings
    {
        return Settings::make();
    }

    /**
     * @param array $config
     * @return array
     */
    public static function setConfig(array $config = []): array
    {
        self::$config = array_merge(self::$config, $config);
        return self::$config;
    }

    /**
     * @param string $key
     * @param $default
     * @return array|mixed|null
     */
    public static function getConfig(string $key = "", $default = null): mixed
    {
        if ($key){
            return self::$config[$key]??$default;
        }else{
            return self::$config;
        }
    }

    /**
     * @param string $key
     * @param $default
     * @return array|mixed|null
     */
    public function config(string $key = "", $default = null): mixed
    {
        return self::getConfig($key,$default);
    }

    /**
     * @param string $path
     * @param string $basePath
     * @return string
     */
    public static function publicPath(string $path, string $basePath = ""):string
    {
        if (isRunInLaravel()){
            $path = trim($path,"/");
            $basePath = trim($basePath,"/");
            return public_path($basePath? $basePath.DIRECTORY_SEPARATOR.$path: $path);
        }else{
            $documentRoot = $_SERVER['DOCUMENT_ROOT']??"";
            if (!$documentRoot&&defined('DOCUMENT_ROOT')){
                $documentRoot = DOCUMENT_ROOT;
            }
            if ($basePath){
                return $documentRoot.DIRECTORY_SEPARATOR.$basePath.DIRECTORY_SEPARATOR.trim($path,"/");
            }else{
                return $documentRoot.DIRECTORY_SEPARATOR.trim($path,"/");
            }
        }
    }

    public static function isInstall(): bool
    {
        $install = self::publicPath("install");
        return file_exists($install) && is_file($install) && trim(file_get_contents($install));
    }

    public static function getConfigPath(string $path = ""): string
    {
        return getLowCodePath($path,"config");
    }

    public static function getTemplatePath(string $path = ""): string
    {
        return getLowCodePath($path,"template");
    }

    /**
     * @param bool $isJsx
     * @param string $sourceDir
     * @return $this
     */
    public function loadVue3SfcLoader(bool $isJsx = false, string $sourceDir = "libs"): static
    {
        $name = "vue3-sfc-loader";
        $suffix = $isJsx?'-jsx':'';
        $js = trim($sourceDir,DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$name.$suffix.".min.js";
        Asset::js($js,$name)->dependsOn('common');
        return $this;
    }

    public function loadCommonJs(string $sourceDir = "libs"): static
    {
        $commonPath = "$sourceDir/common.min.js";
        $filePath = self::publicPath($commonPath);
        if (!file_exists($filePath)){
            $sources = ["vue","axios","pinia","vue-i18n","vue-router","vue-util","naive-ui"];
            $contents = [];
            foreach ($sources as $source){
                $sourcePath = self::publicPath("$sourceDir/$source.min.js");
                if (file_exists($sourcePath)){
                    $contents[] = "/** {$source}.min.js **/\n".file_get_contents($sourcePath);
                }
            }
            if ($contents){
                file_put_contents($filePath,implode("/**__COMMON_JS__**/\n",$contents));
            }
        }
        Asset::js($commonPath,"common");
        return $this;
    }

    public function getApp(): App
    {
        return $this->app;
    }

    public function getManifest(): Manifest
    {
        return $this->manifest;
    }

    public function getUser(): array
    {
        return $this->systemUser;
    }

    /**
     * @param array $systemUser
     * @return $this
     */
    public function setUser(array $systemUser): static
    {
        $this->systemUser = $systemUser;
        return $this;
    }

    public function getManifestPath(): string
    {
        return $this->getManifestDir()."/manifest.json";
    }

    public function getManifestDir(): string
    {
        return trim(self::getConfig("manifestDir","build"),"/");
    }

    /**
     * @param bool $isJsx
     * @param string $sourceDir
     * @return string
     * @throws Exception
     */
    public function getWeb(bool $isJsx = false, string $sourceDir = "libs"): string
    {
        $this->loadCommonJs($sourceDir)->loadVue3SfcLoader($isJsx,$sourceDir);
        $this->manifest->setManifestPath($this->getManifestPath());
        $this->manifest->setConfig($this->getAppConfig());
        $this->manifest->setGlobals(['systemUser'=>$this->getUser()]);
        return $this->manifest->render();
    }

    protected function getAppConfig(): array
    {
      return [
          'debug' => $this->config("debug",false),
          'title' => $this->config("title","LowcodeAdmin"),
          'lang' => $this->config("lang",'zh-CN'),
          'homePage' => $this->config("homePage","/home"),
          'namespace' => $this->config("namespace","lowcode"),
          'baseURL' => $this->config("baseURL","/admin-api"),
          'settings' => $this->config("settings",[]),
      ];
    }

    public function view(): View
    {
        return $this->view;
    }

    public function run(?Request $request = null): void
    {
        $this->app->run($request);
    }

    /**
     * Dynamically handle calls to the class.
     *
     * @param string $method
     * @param array $parameters
     * @return mixed
     *
     * @throws BadMethodCallException
     */
    public function __call(string $method, array $parameters)
    {
        if (method_exists($this->app, $method)){
            return $this->app->{$method}(...$parameters);
        }
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }
}
